Skip to content

Conversation

@dangotbanned
Copy link
Member

@dangotbanned dangotbanned commented Oct 13, 2025

What type of PR is this? (check all applicable)

  • ๐Ÿ’พ Refactor
  • โœจ Feature
  • ๐Ÿ› Bug Fix
  • ๐Ÿ”ง Optimization
  • ๐Ÿ“ Documentation
  • โœ… Test
  • ๐Ÿณ Other

Related issues

Checklist

  • Code follows style guide (ruff)
  • Tests added
  • Documented the changes

If you have comments or can explain your changes, please do so below

Starting off fairly small here, trying to work out if we'll need to implement any other dunders mentioned in Pickling Class Instances

I have had a feeling Enum with deferred categories might need some work.
But, turns out it worked without changes ๐Ÿฅณ (b2ba749)

@dangotbanned dangotbanned marked this pull request as ready for review October 13, 2025 20:00
@dangotbanned
Copy link
Member Author

I'm pleasantly suprised this all worked without changing a single line of (non-test) code ๐Ÿ˜…

I've even tried protocol 3 locally and still no issues.

Can someone burst my bubble and break this? ๐Ÿ™

Copy link
Member

@FBruzzesi FBruzzesi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @dangotbanned - thanks for the initiative! This looks ๐Ÿ”ฅ (no changes in the codebase at all, I am also surprised)

I left a comment that might simplify some tests.

And as:

  1. I know close to nothing about custom pickling
  2. My brain is cooked for the day

I am afraid that the following invite will need to wait for another day or someone else

Can someone burst my bubble and break this? ๐Ÿ™

๐Ÿ˜‚

Comment on lines +35 to +53
class Identity(Protocol):
def __call__(self, obj: IntoDTypeT, /) -> IntoDTypeT: ...


def _roundtrip_pickle(protocol: int | None = None) -> Identity:
def fn(obj: IntoDTypeT, /) -> IntoDTypeT:
result: IntoDTypeT = pickle.loads(pickle.dumps(obj, protocol))
return result

return fn


@pytest.fixture(
params=[_roundtrip_pickle(), _roundtrip_pickle(4), _roundtrip_pickle(5)],
ids=["pickle-None", "pickle-4", "pickle-5"],
)
def roundtrip(request: pytest.FixtureRequest) -> Identity:
fn: Identity = request.param
return fn
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea I had here is we can pretty easily extend this by adding more functions that can roundtrip.

pickle is all that I've used, but the protocols which are being called can be used elsewhere

import narwhals as nw

dtype = nw.Struct({"a": nw.List(nw.Array(nw.String, 5))})

>>> dtype.__reduce_ex__(5)
(<function copyreg.__newobj__(cls, *args)>,
 (narwhals.dtypes.Struct,),
 (None,
  {'fields': [Field('a', List(Array(<class 'narwhals.dtypes.String'>, shape=(5,))))]}),
 None,
 None)


>>> dtype.__getstate__()
(None,
 {'fields': [Field('a', List(Array(<class 'narwhals.dtypes.String'>, shape=(5,))))]})

Copy link
Member Author

@dangotbanned dangotbanned Oct 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Barely on topic, but noticable from the example I used

@FBruzzesi nested DTypes are where the metaclass repr from polars would be nice

import narwhals as nw
>>> nw.Struct({"a": nw.List(nw.Array(nw.String, 5))})
Struct({'a': List(Array(<class 'narwhals.dtypes.String'>, shape=(5,)))})
import polars as pl
>>> pl.Struct({"a": pl.List(pl.Array(pl.String, 5))})
Struct({'a': List(Array(String, shape=(5,)))})

Copy link
Member

@FBruzzesi FBruzzesi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dangotbanned if you are happy with the state of this, I think we can merge these changes for now and iterate on top of them if needed.

Quoting my previous self #3205 (review)

I know close to nothing about custom pickling

@dangotbanned
Copy link
Member Author

dangotbanned commented Oct 15, 2025

@dangotbanned if you are happy with the state of this, I think we can merge these changes for now and iterate on top of them if needed.

Thanks @FBruzzesi!

Quoting my previous self #3205 (review)

I know close to nothing about custom pickling

That shouldn't be an issue, since we're utilizing the default picking โ˜บ๏ธ

object.__getstate__

  • For a class that has __slots__ and no instance __dict__, the default state is a tuple whose first item is None and whose second item is a dictionary mapping slot names to slot values described in the previous bullet.

@dangotbanned dangotbanned merged commit f05011c into main Oct 15, 2025
30 of 31 checks passed
@dangotbanned dangotbanned deleted the dtype-serde branch October 15, 2025 09:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants